﻿#region License

// Copyright (c) 2012, Zhiyong Yang.
// All rights reserved.
// 
// 
// This software is licensed under the GNU General Public License version 2 (GPLv2).
// 
// Contact Me: 
//  Mail: younthu@yahoo.com.cn
//  MSN:  evil_eve_live@hotmail.com

#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BubbelsModel
{
    public class Ball
    {
        public Ball(int x, int y)
        {
            this.X = x;
            this.Y = y;
        }
        public int X;
        public int Y;
        public int Type;
    }
    /// <summary>
    /// 
    /// </summary>
    public class BubbelsTable
    {
        /// <summary>
        /// (Row, Col) cleared
        /// </summary>
        public event Action<int, int> OnBallCleared = delegate { };


        /// <summary>
        /// Occurs when [on ball move horizontal].
        /// (Row, Col, Delta)
        /// </summary>
        public event Action<int, int,int> OnBallMoveHorizontal = delegate { };

        /// <summary>
        /// Occurs when [on ball move vertical].
        /// (Row, Col, Delta)
        /// </summary>
        public event Action<int, int,int> OnBallMoveVertical = delegate { };


        /// <summary>
        /// (Row, Col, Ball)
        /// </summary>
        public event Action<Ball> OnNewBall = delegate { };

        public int Column{get; private set;}
        public int Row{get; private set;}
        public Random Random;
        public int BallTypeCount = 5;
        private int[,] balls;

        public BubbelsTable(int width, int height, int seed)
        {

            this.Column = width;
            this.Row = height;
            balls = new int[height, width];
            Random = new Random(seed);
        }
        public BubbelsTable(int width, int height):this(width, height, (int)DateTime.Now.Ticks)
        {
        }


        public void ClearTable()
        {
            for (int i = 0; i < this.Row; i++)
            {
                for (int j = 0; j < this.Column; j++)
                {
                    this.balls[i, j] = 0;
                }
            }
        }

        public void FillTable()
        {

            for (int row = 0; row < this.Row; row++)
            {
                for (int col = 0; col < this.Column; col++)
                {
                    this.balls[row, col] = NextBall();
                    Ball newBall = new Ball(col, row);
                    newBall.Type = this.balls[row, col];
                    this.OnNewBall(newBall);
                }
            }
        }

        public void FillCell(int col, int row, int value)
        {
            this.balls[row,col] = value;
        }

        public void FillRow(int[] range, int row, int count)
        {
            for (int i = 0; i < count && i < this.Column; i++)
            {
                this.balls[row ,i] = range[i];
            }
        }

        public void FillCol(int[] range, int row, int count)
        { 

        }

        public int GetBall(int col, int row)
        {
            return this.balls[row,col];
        }

        private int CountLeftEmptyColumns()
        {
            int count = 0;
            while (count < this.Column&&this.balls[0, count] == 0)
            {
                count++;
            }

            return count;
        }

        /// <summary>
        /// Removes the empty column and fill new column.
        /// </summary>
        /// <returns> the index of column which was removed</returns>
        public List<int> RemoveEmptyColumnAndFill()
        {
            List<int> result = new List<int>();
            int emptyColumnCount = 0;
            for (int i = 0; i < this.Column; i++)
            {
                if (this.balls[0,i]==0)
                {
                    emptyColumnCount++;
                    result.Add(i);
                }
            }
            while (this.CountLeftEmptyColumns() < emptyColumnCount)
            {
                for (int i = 0; i < Column - 1; i++)
                {
                    // Check if the column is empty
                    if (IsColumnEmpty(Column - 1 - i))
                    {
                        // Move column right
                        MoveColumnRight(Column - 1 - i);


                    }
                }
            }
            

            for (int i = emptyColumnCount-1; i >=0; i--)
            {

                // Fill new column
                FillLeftColumn(i); 
            }

            return result;
        }

        #region Union operatioin
        public List<Ball> ClearUnion(int x, int y)
        { 
            var removedBalls = GetUnion( x, y, this.balls, this.balls[y,x]);
            foreach (var item in removedBalls)
            {
                this.OnBallCleared(item.Y,item.X);
                
            }

            removedBalls.Sort(new Comparison<Ball>(
                (Ball first, Ball second) => { 
                return  second.Y - first.Y; 
            }));
            foreach (var item in removedBalls)
            {
                // Move down balls
                for (int i = item.Y; i < this.Row; i++)
                {
                    if (i+1 >= Row || balls[i+1,item.X] == 0)
                    {
                        break;
                    }


                    this.balls[i, item.X] = this.balls[i + 1, item.X];
                    this.balls[i + 1, item.X] = 0;
                    this.OnBallMoveVertical(i + 1, item.X, -1); 
                }
            }

            this.RemoveEmptyColumnAndFill();

            return removedBalls;
        }

        public List<Ball> GetUnion(int x, int y)
        {
            if (!HasBall(x,y,this.balls))
            {
                return new List<Ball>();
            }
            return GetUnion(x, y, this.balls.Clone() as int[,], this.balls[y,x]);
        }


        private List<Ball> GetUnion(int x, int y, int[,] data, int ball)
        {
            if (!HasBall(x, y, data) || this.balls[y,x]!=ball)
            {
                return new List<Ball>();
            }

            data[y,x] = 0;
            var result = new List<Ball>();
            Ball curr = new Ball(x,y);
            result.Add(curr);
            result.AddRange(GetUnion(x - 1, y,data,ball));
            result.AddRange(GetUnion(x + 1, y,data,ball));
            result.AddRange(GetUnion(x , y-1,data,ball));
            result.AddRange(GetUnion(x , y+1,data,ball));

            return result;
        }

        #endregion Union operation

        #region Private
        private bool ValidatePoint(int x, int y)
        {
            if (x < 0 || y < 0 || x >= Column || y >= Row)
            {
                return false;
            }

            return true;
        }

        private bool HasBall(int x, int y, int[,] data)
        {
            return ValidatePoint(x, y) && data[y,x] != 0;
        }

        private bool IsColumnEmpty(int column)
        {
            if (column < 0 || column >= Column)
            {
                throw new IndexOutOfRangeException("The column is not visible");
            }

            return balls[0, column] == 0; // 0 means empty box
        }

        private void MoveColumnRight(int emptyColumn)
        {
            for (int i = emptyColumn; i > 0; i--)
            {
                for (int j = 0; j < Row; j++)
                {
                    
                    balls[j,i] = balls[j,i-1];
                    balls[j, i - 1] = 0;
                    if (balls[j,i] !=0)
                    {
                        this.OnBallMoveHorizontal(j, i - 1,1);                        
                    }
                }
            }
        }

        private int NextBall()
        {
            return Random.Next() % BallTypeCount + 1;
        }
        private void FillLeftColumn(int col)
        {
            int ballCount = Random.Next() % Row + 1;// new empty column is not acceptable;
            for (int i = 0; i < ballCount; i++)
            {
                balls[i ,col] = NextBall();
                Ball ball = new Ball(col,i);
                ball.Type=this.balls[i,col];
                this.OnNewBall(ball);
            }
        }
        #endregion

        public bool IsGameOver()
        {
            for (int col = 0; col < this.Column - 1; col++)
            {
                for (int row = 0; row < this.Row - 1; row++)
                {
                    if (this.balls[row,col] !=0 &&(this.balls[row,col] == this.balls[row,col+1] || this.balls[row,col]==this.balls[row+1, col]))
                    {
                        return false;
                    }
                }
            }

            // last Row
            for (int i = 0; i < this.Column -1; i++)
            {
                if (this.balls[this.Row-1, i] == this.balls[this.Row-1, i+1] && this.balls[Row-1, i]!=0)
                {
                    return false;
                }
            }

            // Last column
            for (int i = 0; i < this.Row -1; i++)
            {
                if (this.balls[i, this.Column -1] == this.balls[i+1, this.Column-1] && this.balls[i,this.Column-1]!=0)
                {
                    return false;
                }
            }
            return true;
        }
    }
}
